#include "Subsystems/BlockBehaviors/SubsystemPlantBlockBehavior.h"
#include "GameRandom.hpp"
#include "GameSingleton.h"
#include "Subsystems/SubsystemTimeOfDay.h"
#include "Subsystems/SubsystemTerrain.h"
#include "Subsystems/SubsystemGameInfo.h"
#include "Managers/BlocksManager.h"
#include "Log.h"

#include "Blocks/SoilBlock.h"
#include "Blocks/AirBlock.h"
#include "Blocks/GrassBlock.h"
#include "Blocks/DirtBlock.h"
#include "Blocks/SandBlock.h"
#include "Blocks/FenceBlock.h"

#include "Blocks/DryBushBlock.h"
#include "Blocks/LargeDryBushBlock.h"
#include "Blocks/JackOLanternBlock.h"
#include "Blocks/RottenPumpkinBlock.h"

#include "Blocks/TallGrassBlock.h"
#include "Blocks/RedFlowerBlock.h"
#include "Blocks/PurpleFlowerBlock.h"
#include "Blocks/WhiteFlowerBlock.h"
#include "Blocks/RyeBlock.h"
#include "Blocks/CottonBlock.h"
#include "Blocks/PumpkinBlock.h"

namespace PocketSurvival
{

    SubsystemPlantBlockBehavior::SubsystemPlantBlockBehavior()
    {
        m_handleBlocks.push_back(TallGrassBlock::Index());
        m_handleBlocks.push_back(RedFlowerBlock::Index());
        m_handleBlocks.push_back(PurpleFlowerBlock::Index());
        m_handleBlocks.push_back(WhiteFlowerBlock::Index());
        m_handleBlocks.push_back(DryBushBlock::Index());
        m_handleBlocks.push_back(LargeDryBushBlock::Index());           // 99
        m_handleBlocks.push_back(PumpkinBlock::Index());                // 131
        m_handleBlocks.push_back(JackOLanternBlock::Index());           // 132
        m_handleBlocks.push_back(RyeBlock::Index());                    // 174
        m_handleBlocks.push_back(CottonBlock::Index());
        m_handleBlocks.push_back(RottenPumpkinBlock::Index());
    }

    void SubsystemPlantBlockBehavior::onPoll(int32_t value, int32_t x, int32_t y, int32_t z)
    {
        if(m_gameInfo->environmentBehaviorMode == EnvironmentBehaviorMode::Static || y <=0 || y >= 255)
        {
            return;
        }
        int32_t blockContents = Terrain::ExtractContents(value);
        switch (blockContents)
        {
        case TallGrassBlock::Index():
            growTallGrass(value, x, y, z);
            break;
        case RedFlowerBlock::Index():
        case PurpleFlowerBlock::Index():
        case WhiteFlowerBlock::Index():
            growFlower(value, x, y, z);
            break;
        case RyeBlock::Index():
            growRye(value, x, y, z);
            break;
        case CottonBlock::Index():
            growCotton(value, x, y, z);
            break;
        case PumpkinBlock::Index():
            growPumpkin(value, x, y, z);
            break;
        }
    }

    void SubsystemPlantBlockBehavior::onNeighborBlockChanged(int32_t x, int32_t y, int32_t z, int32_t neighborX, int32_t neighborY, int32_t neighborZ)
    {
        int32_t num = Terrain::ExtractContents(m_terrain->getCellValue(x, y, z));
        int32_t cellValue = m_terrain->getCellValue(x, y - 1, z);
        int32_t num2 = Terrain::ExtractContents(cellValue);
        switch (num)
        {
        	case PumpkinBlock::Index():
        	case RottenPumpkinBlock::Index():
        		if (num2 != GrassBlock::Index() && num2 != DirtBlock::Index() && num2 != SoilBlock::Index())
        		{
        			m_terrain->destroyCell(0, x, y, z, 0, false);
        		}
        		break;
        	case JackOLanternBlock::Index():
        		{
        			Block *block = GameSingleton::blocksManager->blocks[num2];
        			if (block->isFaceTransparent(4, cellValue) && block->isBlock<FenceBlock*>() == false)
        			{
        				m_terrain->destroyCell(0, x, y, z, 0, false);
        			}
        			break;
        		}
        	default:
        		if (num2 != GrassBlock::Index() && num2 != DirtBlock::Index() && num2 != SandBlock::Index() && num2 != SoilBlock::Index())
        		{
        			m_terrain->destroyCell(0, x, y, z, 0, false);
        		}
        		break;
        }
    }


    void SubsystemPlantBlockBehavior::update(float dt)
    {
        if (m_timeOfDay->periodicGameTimeEvent(50.0, 0.0))
        {
            m_toReplaceLock.lock();
            for (const auto& pair : m_toReplace)
            {
                if (Terrain::ReplaceLight(m_terrain->getCellValue(pair.first), 0) == Terrain::ReplaceLight(pair.second.requiredValue, 0))
                {
                	m_terrain->changeCell(pair.first, pair.second.value);
                }
            }
        	m_toReplace.clear();
            m_toReplaceLock.unlock();
        }
    }

    void SubsystemPlantBlockBehavior::growTallGrass(int32_t value, int32_t x, int32_t y, int32_t z)
    {
        int32_t data = Terrain::ExtractData(value);
        if (TallGrassBlock::GetIsSmall(data) && Terrain::ExtractLight(m_terrain->getCellValue(x, y + 1, z)) >= 9)
        {
            int32_t data2 = TallGrassBlock::SetIsSmall(data, false);
            int32_t value2 = Terrain::ReplaceData(value, data2);
            m_toReplace[Point3(x, y, z)] = Replacement(value, value2);
        }
    }

    void SubsystemPlantBlockBehavior::growFlower(int32_t value, int32_t x, int32_t y, int32_t z)
    {
        int32_t data = Terrain::ExtractData(value);
        if (FlowerBlock::GetIsSmall(data)  && Terrain::ExtractLight(m_terrain->getCellValue(x, y + 1, z)) >= 9)
        {
            int32_t data2 = FlowerBlock::SetIsSmall(data, false);
            int32_t value2 = Terrain::ReplaceData(value, data2);
            m_toReplace[Point3(x, y, z)] = Replacement(value, value2);
        }
    }

    void SubsystemPlantBlockBehavior::growRye(int32_t value, int32_t x, int32_t y, int32_t z)
    {
        if (Terrain::ExtractLight(m_terrain->getCellValue(x, y + 1, z)) < 9)
        {
        	return;
        }
        int32_t data = Terrain::ExtractData(value);
        int32_t size = RyeBlock::GetSize(data);
        if (size == 7)
        {
        	return;
        }
        Replacement replacement;
        if (RyeBlock::GetIsWild(data))
        {
        	if (size < 7)
        	{
        		int32_t data2 = RyeBlock::SetSize(RyeBlock::SetIsWild(data, true), size + 1);
        		int32_t value2 = Terrain::ReplaceData(value, data2);

                replacement.value = value2;
                replacement.requiredValue = value;

                m_toReplaceLock.lock();
                m_toReplace[Point3(x, y, z)] = replacement;
                m_toReplaceLock.unlock();
        	}
        	return;
        }
        int32_t cellValueFast = m_terrain->getCellValue(x, y - 1, z);
        if (Terrain::ExtractContents(cellValueFast) == SoilBlock::Index())
        {
        	int32_t data3 = Terrain::ExtractData(cellValueFast);
        	bool hydration = SoilBlock::GetHydration(data3);
        	int32_t nitrogen = SoilBlock::GetNitrogen(data3);
        	int32_t num = 3;
        	float num2 = 0.8f;
        	if (nitrogen > 0)
        	{
        		num--;
        		num2 -= 0.4f;
        	}
        	if (hydration)
        	{
        		num--;
        		num2 -= 0.4f;
        	}
        	if (GameSingleton::gameRandom->getInt(1, 65535) % MathUtils::Max<int32_t>(num, 1) == 0)
        	{
        		int32_t data4 = RyeBlock::SetSize(data, MathUtils::Min<int32_t>(size + 1, 7));
        		if (GameSingleton::gameRandom->getFloat(0.0f, 1.0f) < num2 && size == 3)
        		{
        			data4 = RyeBlock::SetIsWild(data4, true);
        		}
        		int32_t value4 = Terrain::ReplaceData(value, data4);

                replacement.value = value4;
                replacement.requiredValue = value;

                m_toReplaceLock.lock();
                m_toReplace[Point3(x, y, z)] = replacement;
                m_toReplaceLock.unlock();

        		if (size + 1 == 7)
        		{
        			int32_t data5 = SoilBlock::SetNitrogen(data3, MathUtils::Max<int32_t>(nitrogen - 1, 0));
        			int32_t value5 = Terrain::ReplaceData(cellValueFast, data5);

                    replacement.value = value5;
                    replacement.requiredValue = cellValueFast;

                    m_toReplaceLock.lock();
        			m_toReplace[Point3(x, y - 1, z)] = replacement;
                    m_toReplaceLock.unlock();
        		}
        	}
        }
        else
        {
        	int32_t value6 = Terrain::ReplaceData(value, RyeBlock::SetIsWild(data, true));
            replacement.value = value6;
            replacement.requiredValue = value;

            m_toReplaceLock.lock();
        	m_toReplace[Point3(x, y, z)] = replacement;
            m_toReplaceLock.unlock();
        }
    }

    void SubsystemPlantBlockBehavior::growCotton(int32_t value, int32_t x, int32_t y, int32_t z)
    {
        if (Terrain::ExtractLight(m_terrain->getCellValue(x, y + 1, z)) < 9)
        {
        	return;
        }
        int32_t data = Terrain::ExtractData(value);
        int32_t size = CottonBlock::GetSize(data);
        if (size >= 2)
        {
        	return;
        }
        Replacement replacement;
        if (CottonBlock::GetIsWild(data))
        {
        	if (size < 2)
        	{
        		int32_t data2 = CottonBlock::SetSize(CottonBlock::SetIsWild(data, true), size + 1);
        		int32_t value2 = Terrain::ReplaceData(value, data2);

                replacement.value = value2;
                replacement.requiredValue = value;

                m_toReplaceLock.lock();
        		m_toReplace[Point3(x, y, z)] = replacement;
                m_toReplaceLock.unlock();
        	}
        	return;
        }
        int32_t cellValueFast = m_terrain->getCellValue(x, y - 1, z);
        if (Terrain::ExtractContents(cellValueFast) == SoilBlock::Index())
        {
        	int32_t data3 = Terrain::ExtractData(cellValueFast);
        	bool hydration = SoilBlock::GetHydration(data3);
        	int32_t nitrogen = SoilBlock::GetNitrogen(data3);
        	int32_t num = 6;
        	float num2 = 0.8f;
        	if (nitrogen > 0)
        	{
        		num -= 2;
        		num2 -= 0.4f;
        	}
        	if (hydration)
        	{
        		num -= 2;
        		num2 -= 0.4f;
        	}
        	if (GameSingleton::gameRandom->getInt(1, 65535) % MathUtils::Max<int32_t>(num, 1) == 0)
        	{
        		int32_t data4 = CottonBlock::SetSize(data, MathUtils::Min<int32_t>(size + 1, 2));
        		if (GameSingleton::gameRandom->getFloat(0.0f, 1.0f) < num2 && size == 1)
        		{
        			data4 = CottonBlock::SetIsWild(data4, true);
        		}
        		int32_t value4 = Terrain::ReplaceData(value, data4);

                replacement.value = value4;
                replacement.requiredValue = value;

                m_toReplaceLock.lock();
        		m_toReplace[Point3(x, y, z)] = replacement;
                m_toReplaceLock.unlock();

        		if (size + 1 == 2)
        		{
        			int32_t data5 = SoilBlock::SetNitrogen(data3, MathUtils::Max<int32_t>(nitrogen - 1, 0));
        			int32_t value5 = Terrain::ReplaceData(cellValueFast, data5);

                    replacement.value = value5;
                    replacement.requiredValue = cellValueFast;

                    m_toReplaceLock.lock();
                    m_toReplace[Point3(x, y - 1, z)] = replacement;
                    m_toReplaceLock.unlock();
        		}
        	}
        }
        else
        {
        	int32_t value6 = Terrain::ReplaceData(value, CottonBlock::SetIsWild(data, true));
            replacement.value = value6;
            replacement.requiredValue = value;

            m_toReplaceLock.lock();
            m_toReplace[Point3(x, y, z)] = replacement;
            m_toReplaceLock.unlock();
        }
    }

    void SubsystemPlantBlockBehavior::growPumpkin(int32_t value, int32_t x, int32_t y, int32_t z)
    {
        if (Terrain::ExtractLight(m_terrain->getCellValue(x, y + 1, z)) < 9)
        {
        	return;
        }
        int32_t data = Terrain::ExtractData(value);
        int32_t size = BasePumpkinBlock::GetSize(data);
        if (BasePumpkinBlock::GetIsDead(data) || size >= 7)
        {
        	return;
        }
        int32_t cellValueFast = m_terrain->getCellValue(x, y - 1, z);
        int32_t num = Terrain::ExtractContents(cellValueFast);
        int32_t data2 = Terrain::ExtractData(cellValueFast);
        int32_t num2 = (num == SoilBlock::Index()) ? SoilBlock::GetNitrogen(data2) : 0;
        int32_t num3 = 4;
        float num4 = 0.15f;
        if (num == 168)
        {
        	num3--;
        	num4 -= 0.05f;
        }
        if (num2 > 0)
        {
        	num3--;
        	num4 -= 0.05f;
        }
        if (num == SoilBlock::Index() && SoilBlock::GetHydration(data2))
        {
        	num3--;
        	num4 -= 0.05f;
        }
        if (GameSingleton::gameRandom->getInt(1, 65535) % MathUtils::Max<int32_t>(num3, 1) == 0)
        {
        	int32_t data3 = BasePumpkinBlock::SetSize(data, MathUtils::Min<int32_t>(size + 1, 7));
        	if (GameSingleton::gameRandom->getFloat(0.0f, 1.0f) < num4)
        	{
        		data3 = BasePumpkinBlock::SetIsDead(data3, true);
        	}
        	int32_t value2 = Terrain::ReplaceData(value, data3);

            Replacement replacement;
            replacement.value = value2;
            replacement.requiredValue = value;

            m_toReplaceLock.lock();
            m_toReplace[Point3(x, y, z)] = replacement;
            m_toReplaceLock.unlock();

            if (num == SoilBlock::Index() && size + 1 == 7)
        	{
        		int32_t data4 = SoilBlock::SetNitrogen(data2, MathUtils::Max<int32_t>(num2 - 3, 0));
        		int32_t value4 = Terrain::ReplaceData(cellValueFast, data4);

                replacement.value = value4;
                replacement.requiredValue = cellValueFast;

                m_toReplaceLock.lock();
                m_toReplace[Point3(x, y - 1, z)] = replacement;
                m_toReplaceLock.unlock();
        	}
        }
    }

    void SubsystemPlantBlockBehavior::load(const nlohmann::json& json)
    {
        m_timeOfDay = GameSingleton::singletonPtr->getSingleton<SubsystemTimeOfDay>();
        m_terrain = GameSingleton::singletonPtr->getSingleton<SubsystemTerrain>();
        m_gameInfo = GameSingleton::singletonPtr->getSingleton<SubsystemGameInfo>();
    }

    static const std::string subsystemName = "PlantBlockBehavior";
    const std::string& SubsystemPlantBlockBehavior::getName() const
    {
        return subsystemName;
    }
    SubUpdateType SubsystemPlantBlockBehavior::getUpdateType()
    {
        return SubUpdateType::MultiThreadUpdate;
    }
}
